/*
  Copyright 2011 fullkawa

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package jp.fullkawa.ksp2html;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;

import org.apache.commons.io.FileUtils;

import jp.fullkawa.ksp.KspEntry;
import jp.fullkawa.ksp2html.exception.NotificationException;
import jp.fullkawa.xnovel.Cut;
import jp.fullkawa.xnovel.Scene;
import jp.fullkawa.xnovel.XNovel;
import jp.fullkawa.xnovel.connector.Close;
import jp.fullkawa.xnovel.content.Sentence;
import jp.fullkawa.xnovel.picture.Figure;
import jp.fullkawa.xnovel.picture.Image;
import jp.fullkawa.xnovel.picture.Picture;
import jp.fullkawa.xnovel.picture.Video;

public class KspParser
{
	private static Map<String, Object> layermap = new HashMap<String, Object>();

	private static String[] lynames = new String[4];
	private static String[] lytypes = new String[4];

	private static Scene scenebuf = new Scene();
	private static int cut_count = 0;

	private static Cut cutbuf = null;
	private static Picture picbuf = null;

	private static String cur_dir;
	private static SrcManager bgman;
	private static SrcManager bgmman;
	private static SrcManager fgman;

	private static int[] scr_width_values = { 640, 800, 512, 1024, 512, 640, 1024, 1280};
	private static int scr_width = 0;

	protected static int getScrWidth() {
		return scr_width;
	}

	/**
	 * テスト用
	 */
	protected static void setCutbuf(Cut cut) {
		KspParser.cutbuf = cut;
	}

	/**
	 * テスト用
	 */
	protected static void setPicbuf(Picture pic) {
		KspParser.picbuf = pic;
	}

	public static void init(String work_name) {
		KspParser.readSetting();

		cur_dir = work_name;
		File cur = new File(cur_dir);
		if (!(cur.exists() && cur.isDirectory()))
		{
			if (!cur.mkdir())
			{
				throw new NotificationException(cur + "の作成に失敗しました。");
			}
		}
		bgman = new SrcManager("bg");
		File bg = new File(cur_dir + File.separator + bgman.getDirName());
		if (!(bg.exists() && bg.isDirectory()))
		{
			if (!bg.mkdir())
			{
				throw new NotificationException(bg + "の作成に失敗しました。");
			}
		}
		fgman = new SrcManager("fg");
		File fg = new File(cur_dir + File.separator + fgman.getDirName());
		if (!(fg.exists() && fg.isDirectory()))
		{
			if (!fg.mkdir())
			{
				throw new NotificationException(fg + "の作成に失敗しました。");
			}
		}
		bgmman = new SrcManager("bgm");
		File bgm = new File(cur_dir + File.separator + bgmman.getDirName());
		if (!(bgm.exists() && bgm.isDirectory()))
		{
			if (!bgm.mkdir())
			{
				throw new NotificationException(bgm + "の作成に失敗しました。");
			}
		}

		KspParser.setCutbuf(null);
		KspParser.setPicbuf(null);

		scenebuf = new Scene();
		scenebuf.setId(work_name);
		Close next = new Close();
		scenebuf.setNext(next);
	}

	public static XNovel parseToXNovel(BufferedReader reader)
		throws NotificationException, IOException
	{
		XNovel xnovel = new XNovel();
		initXNovel(xnovel);

		// 分岐はないので、単一シーンとする
		xnovel.addScene(scenebuf);

		String line;
		while ((line = reader.readLine()) != null)
		{
			KspEntry entry = new KspEntry(line);

			if ("画面サイズ".equals(entry.getKey()))
			{
				int scr_size =  Integer.parseInt(entry.getValue());
				scr_width = scr_width_values[scr_size];
			}
			if ("タイムライン".equals(entry.getKey()))
			{
				cut_count = Integer.parseInt(entry.getValue());
			}

			cutbuf = makeCut(entry.getKey());

			if (cutbuf != null)
			{
				picbuf = makePicture(entry);

				if (";".equals(entry.getKey()) && entry.getValue() != null && entry.getValue().length() > 0)
				{
					cutbuf.addContent(new Sentence(entry.getValue()));
				}
			}
		}

		return xnovel;
	}

	/**
	 * Image / Figure / Video を生成する
	 * value の値が ksp_setting.properties のキーにマッチするとき新規インスタンスが作成され、
	 * 続く"ファイルn"等の key にて各プロパティが設定される
	 */
	protected static Picture makePicture(KspEntry entry)
	{
		Picture pic = null;

		if (entry != null)
		{
			// Pictureインスタンスの作成／取得
			String key = entry.getKey();
			if (key != null && (key.startsWith("レイヤ") || key.startsWith("[SoundLine"))) // 音声は現在対象外
			{
				picbuf = null;
			}
			String value = entry.getValue();
			Object temp = layermap.get(value);
			if (temp instanceof Picture)
			{
				pic = ((Picture) temp).clone();
				pic.setChange(true);
				if (lynames[0].equals(value))
				{
					cutbuf.setPicture(Cut.LAYER_BG_ID, pic);
				}
				else if (lynames[1].equals(value))
				{
					cutbuf.setPicture(Cut.LAYER_0_ID, pic);
				}
				else if (lynames[2].equals(value))
				{
					cutbuf.setPicture(Cut.LAYER_1_ID, pic);
				}
				else if (lynames[3].equals(value))
				{
					cutbuf.setPicture(Cut.LAYER_2_ID, pic);
				}
			}
			if (pic == null)
			{
				pic = picbuf;
			}

			// プロパティの設定
			if (pic != null & key != null && key.startsWith("ファイル"))
			{
				String file_path = collectFile(value, pic);
				pic.setSrc(file_path);
			}

			if (pic instanceof Figure)
			{
				Figure fig = (Figure) pic;

				int tatePos = 2; // 縦表示は無しなので固定値で
				fig.setTatePos(tatePos);

				// kspからは左端の座標しか取れない…
				if (key != null && key.matches("X[0-9]+"))
				{
					int yokoPos = 8;
					int x = Integer.parseInt(value);

					if (x > getScrWidth() * 0.5)
					{
						// 左端が中央より右にあるなら右寄せ
						yokoPos = 9;
					}
					else if (x < getScrWidth() * 0.1)
					{
						// 左端が画面幅10%以内にあるなら左寄せ
						yokoPos = 7;
					}
					else {
						// それ以外は中央表示とする
					}
					// System.out.println("x="+x+" -> pos="+yokoPos+" :"+fig.getSrc());
					fig.setYokoPos(yokoPos);
				}
			}
		}

		return pic;
	}

	/**
	 * 使われている画像ファイルほかをePub用のフォルダに集める
	 * PackePubが日本語のファイル名に対応してないようなので、ファイル名の置換も行う
	 * @param src_path
	 * @param pic
	 * @return
	 */
	protected static String collectFile(String src_path, Picture pic)
	{
		File src = new File(src_path.replace("\"", ""));
		File dist = null;

		String dist_src = null; // こっちの"src"はタグの要素名としてのsrc
		SrcManager srcman = null;

		if (pic instanceof Figure) {
			srcman = fgman;
		}
		else {
			// Image, Video
			srcman = bgman;
		}

		String repd_name = srcman.getReplacedFileName(src.getAbsolutePath());
		if (repd_name == null)
		{
			String ext = src_path.substring(src_path.lastIndexOf('.'));
			String padding = "0000" + Integer.toString(srcman.getRenban());
			repd_name = padding.substring(padding.length() - "0000".length()) + ext;
			try {
				dist = new File(cur_dir + File.separator + srcman.getDirName() + File.separator + repd_name);
				FileUtils.copyFile(src, dist);
				srcman.addFileName(src.getAbsolutePath(), repd_name);
			}
			catch (IOException e) {
				throw new NotificationException(dist + ": " + e.getMessage());
			}
		}
		dist_src = srcman.getDirName() + "/" + repd_name;
		return dist_src;
	}

	/**
	 * 与えられたkeyがItemラベルの時、新規カットが生成され、scenebufに追加される
	 * Itemラベルでない時は最新のカットが返される
	 */
	protected static Cut makeCut(String key)
	{
		Cut cut = null;

		if (key != null && key.startsWith("[Item"))
		{
			cut = new Cut();
			scenebuf.addCut(cut);
			StringBuilder id_builder = new StringBuilder();
			id_builder.append("シーン:" + Integer.toString(cut.getNumber()-1));
			id_builder.append(" [Item" + Integer.toString(cut.getNumber()-1) + "]");
			cut.setId(id_builder.toString());
			System.out.println(" 処理中：" + cut.getNumber() + "/" + cut_count);
		}
		else {
			cut = cutbuf;
		}

		return cut;
	}

	protected static void initXNovel(XNovel xnovel)
	{
		xnovel.setDirection("46");
		xnovel.setTextRotation("tb");
		xnovel.setSupport("iphone,ipad");
	}

	protected static void readSetting() throws NotificationException
	{
		ResourceBundle resource = ResourceBundle.getBundle("ksp_setting");
		lynames[0] = resource.getString("Layerbg_name");
		lytypes[0] = resource.getString("Layerbg_type");
		lynames[1] = resource.getString("Layer0_name");
		lytypes[1] = resource.getString("Layer0_type");
		lynames[2] = resource.getString("Layer1_name");
		lytypes[2] = resource.getString("Layer1_type");
		lynames[3] = resource.getString("Layer2_name");
		lytypes[3] = resource.getString("Layer2_type");

		for (int i=0; i<lynames.length; i++)
		{
			Picture pic = null;

			if ("fg".equals(lytypes[i])) {
				// あくまでテンプレとして。実際にオブジェクトを作成する際はcloneすること！
				pic = new Figure();
			}
			else if ("img".equals(lytypes[i])) {
				// 同上
				pic = new Image();
			}
			else if ("video".equals(lytypes[i])) {
				// 同上
				pic = new Video();
			}
			else {
				String msg = "'" + lytypes[i] + "'というtypeは指定できません。fg / img / video のいずれかで指定してください。";
				throw new NotificationException(msg);
			}

			if (pic != null)
			{
				layermap.put(lynames[i], pic);
			}
		}
	}
}

/**
 * ノベル内で使われる画像ファイル等を管理する
 * @author Furukawa
 *
 */
class SrcManager {

	String dirName;

	int renban = 1;

	/**
	 * ファイル名置換テーブル
	 * (ePubは日本語ファイル名が使えない)
	 */
	Map<String, String> fileNames = new HashMap<String, String>();

	private SrcManager() {}

	SrcManager(String dir_name) {
		this();
		this.setDirName(dir_name);
	}

	public String getDirName() {
		return dirName;
	}

	private void setDirName(String dirName) {
		this.dirName = dirName;
	}

	public int getRenban() {
		return renban;
	}

	public void setRenban(int renban) {
		this.renban = renban;
	}

	private Map<String, String> getFileNames() {
		return fileNames;
	}

	private void setFileNames(Map<String, String> fileNames) {
		this.fileNames = fileNames;
	}

	public String getReplacedFileName(String src_name) {
		return this.fileNames.get(src_name);
	}

	public void addFileName(String src_name, String repd_name) {
		this.fileNames.put(src_name, repd_name);
		this.renban++;
	}
}
